
var util_extend = function () {
    var self = this;
    var options, name, src, copy, copyIsArray, clone,
        target = arguments[0] || {},
        i = 1,
        length = arguments.length,
        deep = false;

    // Handle a deep copy situation
    if (typeof target === "boolean") {
        deep = target;

        // Skip the boolean and the target
        target = arguments[i] || {};
        i++;
    }

    // Handle case when target is a string or something (possible in deep
    // copy)
    if (typeof target !== "object" && !self.isFunction(target)) {
        target = {};
    }

    // Extend self itself if only one argument is passed
    if (i === length) {
        target = this;
        i--;
    }

    for (; i < length; i++) {

        // Only deal with non-null/undefined values
        if (( options = arguments[i] ) != null) {

            // Extend the base object
            for (name in options) {
                src = target[name];
                copy = options[name];

                // Prevent never-ending loop
                if (target === copy) {
                    continue;
                }

                // Recurse if we're merging plain objects or arrays
                if (deep && copy && ( self.isPlainObject(copy) ||
                    ( copyIsArray = self.isArray(copy) ) )) {

                    if (copyIsArray) {
                        copyIsArray = false;
                        clone = src && self.isArray(src) ? src : [];

                    } else {
                        clone = src && self.isPlainObject(src) ? src : {};
                    }

                    // Never move original objects, clone them
                    target[name] = self.extend(deep, clone, copy);

                    // Don't bring in undefined values
                } else if (copy !== undefined) {
                    target[name] = copy;
                }
            }
        }
    }

    // Return the modified object
    return target;
}


var _BitrateEstimater = {
  bitrate: 0,
  lastBytes: null,
  lastTimestamp: null,
  updateBytes: function(bytes, now){
    var self = this;
        if (self.lastTimestamp && (now - self.lastTimestamp)>=300) {
          self.bitrate = 8 * (bytes - self.lastBytes) / (now - self.lastTimestamp);
          self.bitrate = Math.floor(self.bitrate);
        }
        self.lastBytes = bytes;
        self.lastTimestamp = now;
    return self.bitrate;
  }
}

var BitrateEstimater = function () {
    util_extend(this, _BitrateEstimater);
};

var _WebrtcStatistics = {
  audioRecvBpsEst: null,
  audioSentBpsEst: null,
  videoRecvBpsEst: null,
  videoSentBpsEst: null,

  reset: function () {
    var self = this;
    self.audioRecvBpsEst = new BitrateEstimater();
    self.audioSentBpsEst = new BitrateEstimater();
    self.videoRecvBpsEst = new BitrateEstimater();
    self.videoSentBpsEst = new BitrateEstimater();
  },

  parseInboundRTP(now, report, bpsEst, callbackRecv, keyName) {
        if(report.bitrateMean){
          var bps = Math.floor(report.bitrateMean / 1024);
          callbackRecv(keyName, bps + ' kbps'); 
        }else if(report.bytesReceived){
          var bps = bpsEst.updateBytes(report.bytesReceived, now); 
          callbackRecv(keyName, bps + ' kbps'); 
        }
  },

  parseOutboundRTP(now, report, bpsEst, callbackSent, keyName) {
        // if(report.bitrateMean){
        //   var bps = Math.floor(report.bitrateMean / 1024);
        //   callbackSent(keyName, bps + ' kbps aa'); 
        // }else if(report.bytesSent){
        //   var bps = bpsEst.updateBytes(report.bytesSent, now); 
        //   callbackSent(keyName, bps + ' kbps bb'); 
        // }

        if(report.bytesSent && !report.isRemote){
          var bps = bpsEst.updateBytes(report.bytesSent, now); 
          callbackSent(keyName, bps + ' kbps'); 
        }
  },

  parseReport(self, results, report, callbackRecv, callbackSent){
      var now = report.timestamp;
      // var milliseconds = now.getUTCMilliseconds();
      var milliseconds = (new Date()).valueOf();

      // if(report.type === 'ssrc' ){
      //   console.log("report=", report);
      // }
      
      
      if (report.type === 'inboundrtp') {
        // firefox calculates the bitrate for us
        // https://bugzilla.mozilla.org/show_bug.cgi?id=951496

        if(report.mediaType === 'video'){
          self.parseInboundRTP(now, report, self.videoRecvBpsEst, callbackRecv, 'video-bps');
        }else if(report.mediaType === 'audio'){
          self.parseInboundRTP(now, report, self.audioRecvBpsEst, callbackRecv, 'audio-bps');
        }

        if(report.framerateMean){
          callbackRecv('video-fps', parseInt(report.framerateMean) + '(' + milliseconds + ')');
        }

        
      }else if (report.type === 'outboundrtp') {
        if(report.mediaType === 'video'){
          self.parseOutboundRTP(now, report, self.videoSentBpsEst, callbackSent, 'video-bps');
        }else if(report.mediaType === 'audio'){
          self.parseOutboundRTP(now, report, self.audioSentBpsEst, callbackSent, 'audio-bps');
        }

        if(report.framerateMean){
          callbackSent('video-fps', parseInt(report.framerateMean) + '(' + milliseconds + ')');
        }

      }else if (report.type === 'ssrc' 
        && (report.mediaType === 'video' || report.googFrameWidthReceived || report.googFrameRateSent)) {
        if(report.googFrameWidthReceived){
          callbackRecv('video-size', report.googFrameWidthReceived + 'x' + report.googFrameHeightReceived);
        }

        if(report.googFrameRateDecoded){
          callbackRecv('video-fps', report.googFrameRateDecoded + '(' + milliseconds + ')');
        }

        if(report.googFrameRateSent){
          callbackSent('video-fps', report.googFrameRateSent + '(' + milliseconds + ')');
        }

        if(report.bytesReceived){
          var bps = self.videoRecvBpsEst.updateBytes(report.bytesReceived, now);  
          callbackRecv('video-bps', bps + ' kbps');
        }

        if(report.bytesSent){
          var bps = self.videoSentBpsEst.updateBytes(report.bytesSent, now);  
          callbackSent('video-bps', bps + ' kbps');
        }

      }else if(report.type === 'ssrc' 
        && (report.mediaType === 'audio' || report.audioOutputLevel || report.audioInputLevel)){
        if(report.bytesReceived){
          var bps = self.audioRecvBpsEst.updateBytes(report.bytesReceived, now);  
          callbackRecv('audio-bps', bps + ' kbps');
        }

        if(report.bytesSent){
          var bps = self.audioSentBpsEst.updateBytes(report.bytesSent, now);  
          callbackSent('audio-bps', bps + ' kbps');
        }
        
      }

      if (report.type === 'candidatepair' && report.selected ||
          report.type === 'googCandidatePair' &&
          report.googActiveConnection === 'true') {
        if(report.remoteCandidateId){
          var remoteCandidate;
          if(results.get){
            remoteCandidate = results.get(report.remoteCandidateId);
          }else{
            remoteCandidate = results[report.remoteCandidateId];
          }
          
          if (remoteCandidate && remoteCandidate.ipAddress &&
              remoteCandidate.portNumber) {
            callbackRecv('peer', remoteCandidate.ipAddress + ':' + remoteCandidate.portNumber 
              + "," + remoteCandidate.candidateType
              + "," + remoteCandidate.transport
              );
          }
        }
      }
  },

  parseRecvStatistics: function (results, callbackRecv, callbackSent) {
    var self = this;

    if(results.forEach){
      results.forEach(function(report){
        self.parseReport(self, results, report, callbackRecv, callbackSent);
      });
    }else{
      Object.keys(results).forEach(function(result) {
        var report = results[result];
        self.parseReport(self, results, report, callbackRecv, callbackSent);

      });
    }

  }
}

var WebrtcStatisticsSection = function () {
    var obj = util_extend(this, _WebrtcStatistics);
    obj.reset();
    return obj;
};

























function utilFireEvent_(o, listener, func){
    if(listener && listener[func]){
      var arr = [o];
      for(var i = 3; i < arguments.length; i++){
        arr.push(arguments[i]);
      }
      listener[func].apply(listener, arr);
    }
};



var offerOptions_ = {
  offerToReceiveAudio: 1,
  offerToReceiveVideo: 1
};

var sdpConstraints_ = {
  'mandatory': {
    'OfferToReceiveAudio': true,
    'OfferToReceiveVideo': true
  }
};

var pc_config = {};
pc_config.iceServers=[]; // 
// pc_config.iceServers.push({"url": "stun:121.41.87.159:3478"});
// pc_config.iceTransportPolicy = 'all'; // 'all' , 'relay' , 'none'
pc_config.rtcpMuxPolicy = 'require'; // 'negotiate' , 'require'
pc_config.bundlePolicy = 'max-bundle';


var ConnSide_ = {
  name: null,
  videoTag: null,
  audioCanvas: null,
  stream: null,
  audioVisualizer: null,
  candidates: null,
  initMe:function(name){
    var self = this;
    self.name = name;
    self.candidates = [];
  },
};

var ConnSide = function (name) {
    var obj = util_extend(this, ConnSide_);
    obj.initMe(name);
    return obj;
};

var WebRTCPeer = function (uid, url, name, confrId) {
    var obj = util_extend(this, WebRTCPeer_);
    obj.initMe(uid, url, name, confrId);
    return obj;
};


var WebRTCPeer_ = {
  uid: null,
  xswitch_server_url: null,
  memberId: null,
  confrId: null,
  isOfferRole: null,

  replace_ip_from: null,
  replace_ip_to: null,

  local: null,
  remote: null,
  pc: null,
  currentRtcId: null,
  subsrId: null,
  isSentLocalSDP: null,

  listener: null,
  isSetup: null,


  initMe: function (uid, url, name, confrId) {
    var self = this;
    if(uid){
      self.uid = uid;
    }else{
      self.uid = Math.random().toString(36).substr(2,4);
    } 
    self.xswitch_server_url = url;
    self.memberId = name;
    self.confrId = confrId;

    self.isOfferRole = true;
    self.isSentLocalSDP = false;
    self.local = new ConnSide('local');
    self.remote = new ConnSide('remote');
    self.isSetup = false;
  }, // initMe

  setLocalStream: function(stream) {
    var self = this;
    self.local.stream = stream;
    self.checkStream(self.local);
  },

  setLocal: function(video_, canvas_){
    var self = this;
    self.local.videoTag = video_;
    self.local.audioCanvas = canvas_;
    self.checkStream(self.local); 
  },

  setRemote: function(video_, canvas_){
    var self = this;
    self.remote.videoTag = video_;
    self.remote.audioCanvas = canvas_;
    self.checkStream(self.remote); 
  },

  setSubsrId: function(subsrId){
    var self = this;
    if(!self.subsrId && subsrId){
      self.subsrId = subsrId;
      if(self.isSetup){
        self.sendSubC(self.subsrId);
      }
    }
  },

  checkStream: function(p){
    var self = this;
    if(!p.stream) {
      if(p.videoTag){
        if(p.videoTag.srcObject){
          self.log('detach stream of ', p.name);
          delete p.videoTag.srcObject;
          p.videoTag.srcObject = null;
        }
      }

      if(p.audioVisualizer){
        console.log('stopped visualizer of', p.name);
        p.audioVisualizer.stop();
        p.audioVisualizer = null;
      }

      return;
    }

    if(p.videoTag){
      if(!p.videoTag.srcObject){
        self.log('attach stream of ', p.name);
        p.videoTag.srcObject = p.stream;
      }
    }
    
    if(p.audioCanvas){
      if(!p.audioVisualizer){
        self.log('new visualizer of', p.name);
        p.audioVisualizer = new StreamVisualizer(p.stream, p.audioCanvas);
        p.audioVisualizer.start();
      }
    }

  }, // checkStream


  log: function(){
    // arguments.unshift('jjj');
    // arguments.splice(0,0,"kkk")
    // console.log.apply(console, arguments);

    var self = this;
    var arr = ['|peer-'+self.uid+'|'];
    for(var i = 0; i < arguments.length; i++){
      arr.push(arguments[i]);
    }
    console.log.apply(console, arr);

  },

  kickConnect: function(audioOn, videoOn, desktopStreamId) {
    var self = this;

    if((audioOn || videoOn || desktopStreamId) && !self.local.stream){
      self.openCapturer(audioOn, videoOn, desktopStreamId);
    }else{
      self.kickInit();
    }
  },

  kickInit:function(){
    var self = this;
    if(self.isOfferRole){
      self.createMyOffer();
    }else{
      self.sendInitC(null);
    }
  },

  createMyOffer: function (){
    var self = this;

    self.maybeCreatePeerconnection();

    if(self.local.stream){
      self.log('Added local stream to pc1');
      self.pc.addStream(self.local.stream);
    }



    self.log('pc1 createOffer start');
    self.pc.createOffer(offerOptions_).then(
    // pc1.createOffer().then(
      function (desc){
        self.log('Offer from pc1\n', desc);
        // localSDPTextarea.value = JSON.stringify( desc );
        self.setLocalSdp(desc);
      }
      , function (error){
        self.log('Failed to create offer: ', error);
      }
    );
  }, // createMyOffer

  createMyAnswer: function (){
    var self = this;
    if(self.local.stream){
      var videoTracks = self.local.stream.getVideoTracks();
      var audioTracks = self.local.stream.getAudioTracks();
      if (videoTracks.length > 0) {
        self.log('Using video device: ' + videoTracks[0].label);
      }
      if (audioTracks.length > 0) {
        self.log('Using audio device: ' + audioTracks[0].label);
      }

      self.log('Added local stream to pc1');
      self.pc.addStream(self.local.stream);
    }
    


    self.createAnswer(
      function (desc){
        self.log('answer from pc1\n' + desc.sdp);
        // localSDPTextarea.value = JSON.stringify( desc );
        self.setLocalSdp(desc);
      }
      , function (error){
        self.log('createAnswer error : ', error);
      }
      , sdpConstraints_
    );
  }, // createMyAnswer


  checkSDPOffer: function (desc){
    return desc.type === 'offer';
  },

  setLocalSdp: function (desc) {
    var self = this;
    self.log('setLocalDescription ...');
    self.pc.setLocalDescription(desc
      , function() {
        self.log('setLocalDescription success');
        if(self.checkSDPOffer(desc)){
          self.sendInitC(desc);   
        }else{
          self.sendAnsC(desc);
        }
      }
      , function(error){
        self.log('setLocalDescription error: ', error);
      });
  }, // setLocalSdp


  maybeCreatePeerconnection: function (){
    var self = this;
    if(pc) {
      return;
    }



    self.pc = new RTCPeerConnection(pc_config);
    self.log('create RTCPeerConnection');
    
    var pc = self.pc;
    var pc1 = self.pc;

    pc1.onicecandidate = function(e) {
      if (event.candidate) {
        self.log('local ICE candidate:',  event);
        // localCandidatesTextarea.value = event.candidate;
        self.local.candidates.push(event.candidate);
        self.maybeSendLocalCandidates();
      }
    };

    
    pc1.oniceconnectionstatechange = function(e) {
      self.log(' ICE state: ' + pc.iceConnectionState);
      // pc1IceStateDiv.textContent += ' => ' + pc.iceConnectionState;
      if(pc.iceConnectionState === 'connected' && !self.isSetup){
        self.isSetup = true;
        utilFireEvent_(self, self.listener, 'onPeerIceSetup');  
        if(self.subsrId){
          self.sendSubC(self.subsrId);
        }
      }
      utilFireEvent_(self, self.listener, 'onPeerIceStateChanged', e);

    };

    pc1.onsignalingstatechange = function(){
      var state;
      state = pc.signalingState || pc.readyState;
      self.log('pc1 state change, state: ' + state);
      // pc1StateDiv.textContent += ' => ' + state;
    };

    pc1.onaddstream = function (e){
      self.log('received remote stream', e);
      self.remote.stream = e.stream;
      self.checkStream(self.remote); 
    };

  }, // maybeCreatePeerconnection

  maybeSendLocalCandidates: function (){
    var self = this;
    if(!self.currentRtcId || !self.isSentLocalSDP){
      return ;
    }

    var candidates = self.local.candidates;
    self.local.candidates = [];

    self.log('sending candidates: ', candidates);

    var msg = {};
    msg.op = 'tcklC';
    msg.rtcId = self.currentRtcId;
    msg.candidates = candidates;
    $.post( self.xswitch_server_url, JSON.stringify( msg )
      , function ( data ){
        console.log('response tcklC: ', data);
      } );
  },

  maybeSetRemoteCandidates: function (){
    var self = this;

    if(!self.isSetRemoteSDP || !self.isSentLocalSDP){
      return ;
    }

    var candidates = self.remote.candidates;
    self.remote.candidates = [];
    candidates.forEach(function(e){
      if(self.replace_ip_from){
        //e.candidate = e.candidate.replace(/215.128.35.8/g, "103.28.214.152");
        e.candidate = e.candidate.replace(self.replace_ip_from, self.replace_ip_to);      
      }

      var c = new RTCIceCandidate(e);
      self.log('add remote candidate', c);
      self.pc.addIceCandidate(c); 
    });
  },



  sendInitC: function (desc){
    var self = this;
      var msg = {};
      msg.op = 'initC';
      msg.tsxId = 'jjkkll-111';
      if(desc){
        msg.sdp = desc;  
      }
      // msg.path = 'rrr';
      // msg.fname = 'a';
      
      self.log('sending initC', msg);
      $.post( self.xswitch_server_url, JSON.stringify( msg )
        , function ( data ){
          self.log('response initC', data);
          if(desc){
            self.afterSendinngOffer(data);
          }else{
            self.afterGotRemoteOffer(data);
          }

        } );
  }, // sendInitC

  sendAnsC: function (desc){
    var self = this;
        var msg = {};
        msg.rtcId = self.currentRtcId;
        msg.op = 'ansC';
        if(desc){
          msg.sdp = desc;  
        }
        
        self.log('sending ansC', msg);
        $.post( self.xswitch_server_url, JSON.stringify( msg )
          , function ( data ){
            self.log('response ansC', data);
            self.isSentLocalSDP = true;
            self.setRemoteJson(data);
            self.maybeSendLocalCandidates();
          } );
  },

  sendSubC: function (subsrId){
    var self = this;
        var msg = {};
        msg.rtcId = self.currentRtcId;
        msg.op = 'subC';
        msg.pubRtcId = subsrId;
        
        self.log('sending subC', msg);
        $.post( self.xswitch_server_url, JSON.stringify( msg )
          , function ( data ){
            self.log('response subC', data);
          } );
  },


  afterSendinngOffer: function (data){
    var self = this;

    self.isSentLocalSDP = true;
    if(data.rtcId){
      self.currentRtcId = data.rtcId;
      self.log('update rtcId: ', self.currentRtcId);
    }
    self.setRemoteJson(data);
    self.maybeSetRemoteCandidates();
    self.maybeSendLocalCandidates();
  },

  afterGotRemoteOffer: function (data){
    var self = this;
    if(data.rtcId){
      self.currentRtcId = data.rtcId;
      self.log('update rtcId: ', self.currentRtcId);
    }
    self.setRemoteJson(data);
  },

  setRemoteJson: function (data){
    var self = this;

    if(data.candidates){
      data.candidates.forEach(function(e){
        self.remote.candidates.push(e);
      });
    }

    if(data.sdp){
      var desc = new RTCSessionDescription(data.sdp);
      self.log('setRemoteDescription: ', desc);
      self.pc.setRemoteDescription(desc
        , function() {
          self.log('setRemoteDescription success');
          self.isSetRemoteSDP = true;
          self.maybeSetRemoteCandidates();

          if(self.checkSDPOffer(desc)){
            self.createMyAnswer();
          }
        }
        , function(error){
          self.log('setRemoteDescription error: ', error);
        });
    }

    self.maybeSetRemoteCandidates();
  },

  hangup: function () {
    var self = this;
    self.log('Ending call');

    if(self.local.audioVisualizer){
      self.log('stopped localStreamVisualizer');
      self.local.audioVisualizer.stop();
      self.local.audioVisualizer = null;
    }

    if(self.remote.audioVisualizer){
      self.log('stopped remoteStreamVisualizer');
      self.remote.audioVisualizer.stop();
      self.remote.audioVisualizer = null;
    }

    if(self.local.stream){
      self.log('stopped localStream');
      self.local.stream.getTracks().forEach(function (track) {
                      track.stop();
                  });
      // self.local.stream.stop();
      self.local.stream = null;
    }

    if(self.remote.stream){
      self.log('stopped remoteStream');
      self.remote.stream.getTracks().forEach(function (track) {
                      track.stop();
                  });

      self.remote.stream = null;
    }

    if(self.local.videoTag && self.local.videoTag.srcObject){
      self.log('stopped localVideo.srcObject');
      self.local.videoTag.srcObject = null;
    }

    if(self.remote.videoTag && self.remote.videoTag.srcObject){
      self.log('stopped remoteVideo.srcObject');
      self.remote.videoTag.srcObject = null;
    }

    if(self.pc){
      if(self.currentRtcId){
        var msg = {};
        msg.op = 'termC';
        msg.rtcId = self.currentRtcId;
        $.post( self.xswitch_server_url, JSON.stringify( msg )
          , function ( data ){
            self.log('response termC: ', data);
            utilFireEvent_(self, self.listener, 'onPeerClosed');
          } );
        self.currentRtcId = null;
      }

      // pc1StateDiv.textContent += ' => ' + pc1.signalingState || pc1.readyState;
      // pc1IceStateDiv.textContent += ' => ' + pc1.iceConnectionState;

      self.pc.close();
      self.pc = null;
    }

    // cleanStatistics();
    // exitButton.disabled = false;
  }, // hangup

  openCapturer: function (audioOn, videoOn, desktopStreamId) {
      var self = this;
      self.log('Requesting local stream');

      var constraints;
      if(audioOn || videoOn){
        constraints = { audio: audioOn, video: videoOn};
      }else if(desktopStreamId){
        constraints = {
          audio: false,
          video: {
              mandatory: {
                  chromeMediaSource: 'desktop',
                  chromeMediaSourceId: desktopStreamId,
                  maxWidth: window.screen.width > 1920 ? window.screen.width : 1920,
                  maxHeight: window.screen.height > 1080 ? window.screen.height : 1080
              },
              optional: []
          }
        };
      }

      navigator.mediaDevices.getUserMedia(constraints)
        .then(function (stream) {
        
        // dump local stream
        self.log('got local stream', stream);
        var videoTracks = stream.getVideoTracks();
        var audioTracks = stream.getAudioTracks();
        if (videoTracks) {
          self.log('local videoTracks: ', videoTracks);
        }
        if (audioTracks) {
          self.log('local audioTracks: ', audioTracks);
        }

        self.setLocalStream(stream);
        self.kickInit();
      }).catch(function(e) {
        self.log('openCapturer error: ', e);
        // alert('openCapturer error: ' + e.name);
        utilFireEvent_(self, self.listener, 'onError', e);
      });
  }, // openCapturer


}; // WebRTCPeer_



var XConferenceSession = function () {
    var obj = util_extend(this, XConferenceSession_);
    obj.initMe();
    return obj;
};

var XConferenceSession_ = {
  uid: null,
  sessId: null,
  userId: null,
  nickName: null,
  ticket: null,
  myPeer: null,
  myVideo: null,
  myCanvas: null,
  listener: null,
  currentCVer: null,
  currentMembers: null,
  currentStreams: null,
  currPeers: null,
  timerId: null,
  desktopPeers:[],

  initMe: function(){
    var self = this;
    self.uid = Math.random().toString(36).substr(2,4);
    self.currentCVer = 0;
    self.currentMembers = {};
    self.currentStreams = {};
    self.currPeers = {};
    self.timerId = -1;
  },


  // public functions  ==>

  setListener: function(listener){
    var self = this;
    self.listener = listener;
  },

  setLocal : function(video, canvas){
    var self = this;
    self.myVideo = video;
    self.myCanvas = canvas;
    if(self.myPeer){
      self.myPeer.setLocal(video, canvas);
    }
  },

  joinConference: function( userIdRes, nickName, ticket, myPublishId){
    var self = this;
    self.ticket = ticket;
    self.userId = self.ticket.userId;
    self.nickName = nickName;

    if(myPublishId){
      self.maybeCreateMyPeer();
      self.myPeer.myPublishId = myPublishId;
      self.myPeer.kickConnect(true, true);
      // self.openCapturer(true, true);
    }else{
      self.fireJoined('directly');
    }
  },

  exitConference: function(reason){
    var self = this;
    
    // stop timer
    self.stopTimer();

    // remove members
    var rmMemIds=[];
    for(var k in self.currentMembers){
      rmMemIds.push(self.currentMembers[k].memId);
    }
    for(var k in rmMemIds){
      self.removeMember(rmMemIds[k]);
    }

    // close desktop peers
    var rmPeerIds=[];
    for(var k in self.desktopPeers){
      rmPeerIds.push(self.desktopPeers[k].uid);
    }
    for(var k in rmPeerIds){
      self.removePeer(rmPeerIds[k]);
    }

    // close myPeer
    if(self.myPeer){
      self.removePeer(self.myPeer.uid);
    }
    utilFireEvent_(self, self.listener, 'onExitd', self.sessId);
  },

  subscribeStream: function(streamId, videoTag, audioCanvas){
    var self = this;
    
    var stream = self.getStream(streamId);
    if(!stream){
      self.log('subsr non-exist streamId ', streamId);
      return;
    }
    var memId = stream.memId;

    self.log('subsr streamId ', streamId);
    var peer = self.newPeer(stream);
    stream.subsrPeer = peer;
    peer.setRemote(videoTag, audioCanvas);
    peer.setSubsrId(streamId);
    peer.kickConnect(false, false);
  },

  publishDesktop: function(myPublishId, videoTag){
    var self = this;
    rsdChooseDesktopMedia(null, function(desktopStreamId){
      if(!desktopStreamId){
        //TODO: fire error event
        return;
      }
      var peer = self.newPeer(null, null); 
      self.desktopPeers.push(peer);
      peer.myPublishId = myPublishId;
      peer.setLocal(videoTag, null);
      peer.kickConnect(false, false, desktopStreamId);
    });
  },

  unpublish: function(myPublishId){
    var self = this;
    if(self.myPeer && self.myPeer.myPublishId && self.myPeer.myPublishId === myPublishId){
      self.removePeer(self.myPeer.uid);
      return;
    }

    for(var k in self.desktopPeers){
      var peer = self.desktopPeers[k];
      if(peer.myPublishId && peer.myPublishId === myPublishId){
        self.removePeer(peer.uid);
      }
    }
  },

  unsubscribeStream: function(streamId){
    var self = this;
    
    var stream = self.getStream(streamId);
    if(!stream){
      self.log('unsubsr non-exist streamId ', streamId);
      return;
    }
    if(!stream.subsrPeer){
      self.log('unsubsr non-exist peer ');
      return;
    }

    self.log('unsubsr streamId ', streamId);
    self.removePeer(strea.subsrPeer.uid);
    stream.subsrPeer = null;
  }, 

  // public functions  <==




  makeSessionId: function(){
    var self = this;
    self.sessId = 'sess-'+Math.random().toString(36).substr(2,4);
  },


  maybeCreateMyPeer: function(){
    var self = this;
    if(!self.myPeer){
      self.myPeer = self.newPeer(null, self.userId); 
      self.myPeer.setLocal(self.myVideo, self.myCanvas);
    }
  },

  newPeer: function(subsrStream, id){
    var self = this;
    var memId = null;
    var peerId = id;
    if(subsrStream){
      memId = subsrStream.memId;
      peerId = subsrStream.id;
    }
    var peer = new WebRTCPeer(peerId, self.ticket.url, memId, self.ticket.confrId);
    peer.listener = self;
    peer.subsrStream = subsrStream;
    self.currPeers[peer.uid] = peer;
    self.log('new peer ', peer.uid);
    return peer;
  },

  removePeer: function(peerId){
    var self = this;
    var peer = self.currPeers[peerId];
    if(!peer){
      self.log('remove peer non-exit peerId ', peerId);
      return;
    }
    peer.hangup();
    self.log('remove peer ', peer.uid);
    if(peerId === self.myPeer.uid){
      self.log('remove myPeer ', peer.uid);

      if(peer.myStreamId){
        self.log('fire my publish event: stop, myPublishId=', peer.myPublishId);
        utilFireEvent_(self, self.listener, 'onStreamEvent', self.sessId, self.userId, peer.myPublishId, 'onStreamStopPublish' );
      }

      self.myPeer = null;
    }

    for(var k in self.desktopPeers){
      var p = self.desktopPeers[k];
      if(p && p.uid === peerId){
        self.log('remove desktop peer ', p.uid);
        if(p.myStreamId){
          self.log('fire desktop publish event: stop, myPublishId=', p.myPublishId);
          utilFireEvent_(self, self.listener, 'onStreamEvent', self.sessId, self.userId, p.myPublishId, 'onStreamStopPublish' );
        }
        delete self.desktopPeers[k];
        break;
      }
    }
    delete self.currPeers[peerId];
  },

  
  getMember: function(memId){
    var self = this;
    return self.currentMembers[memId];
  },

  addMember: function(newm){
    var self = this;
    var streams = newm.streams;
    newm.streams = {};
    self.currentMembers[newm.memId] = newm;
    self.log('confr change: add member ', newm);
    utilFireEvent_(self, self.listener, 'onMemberAttended', self.sessId, newm.memId);
    // for(var i = 0; i < streams.length; i++){
    //   self.addStream(newm.memId, streams[i]);
    // }
  },

  removeMember: function(memId){
    var self = this;
    var m = self.getMember(memId);
    if(!m) return null;

    var rmStreamIds = [];
    for(k in self.currentStreams){
      var s = self.currentStreams[k];
      if(s.memId === memId){
        rmStreamIds.push(s.id);  
      }
    }

    for(k in rmStreamIds){
      self.removeStream(rmStreamIds[k]);
    }

    self.log('confr change: remove member ', m);
    delete self.currentMembers[memId];
    utilFireEvent_(self, self.listener, 'onMemberLeave', self.sessId, memId);

    return m;
  },

  getStream: function(streamId){
    var self = this;
    var s = self.currentStreams[streamId];
    if(!s) return null;
    return s;
  }, 

  getPublishPeerByStream: function(stream){
    var self = this;
    if(stream.memId === self.userId){
      var streamId = stream.id;
      if(self.myPeer && self.myPeer.myPublishId && self.myPeer.currentRtcId === streamId){
        return self.myPeer;
      }
      for(var k in self.desktopPeers){
        var peer = self.desktopPeers[k];
        if(peer && peer.myPublishId && peer.currentRtcId === streamId){
          return peer;
        }
      }
    }
    return null;
  },

  addStream: function(stream){
    var self = this;
    var memId = stream.memId;
    var m = self.getMember(memId);
    if(m){
      self.currentStreams[stream.id] = stream;
      self.log('confr change: add stream', stream);

      var streamId = stream.id;
      var pubpeer = self.getPublishPeerByStream(stream);
      if(pubpeer){
        stream.myPublishId = pubpeer.myPublishId;
        self.log('confr change: add stream but change id [' + streamId + '] -> [' + stream.myPublishId + ']');
        streamId = stream.myPublishId;
        return true;
      }else if(memId === self.userId){
        self.log('kick by other device');
        self.exitConference();
        utilFireEvent_(self, self.listener, 'onError', self.sessId, 'kick');
        return false;
      }

      utilFireEvent_(self, self.listener, 'onStreamEvent', self.sessId, memId, streamId, 'onStreamStartPublish' );
    }else{
      self.log('addStream non-exist memId ', memId);
    }
    return true;
  },

  removeStream: function(streamId){
    var self = this;
    var s = self.currentStreams[streamId];
    if(!s) return null;

    if(s.subsrPeer){
      self.removePeer(s.subsrPeer.uid);
      s.subsrPeer = null;
    }

    var memId = s.memId;
    self.log('confr change: remove stream', s);
    delete self.currentStreams[streamId];
    
    if(s.myPublishId){
      self.log('confr change: remove stream but change id [' + streamId + '] -> [' + s.myPublishId + ']');
      streamId = s.myPublishId;
      return;
    }

    utilFireEvent_(self, self.listener, 'onStreamEvent', self.sessId, memId, streamId, 'onStreamStopPublish');
    return s;
  },

  updateMembers: function(mems, cver){
    var self = this;
    if(typeof mems === 'undefined'){
      return; // TODO: check array type
    }


    var memMap={};
    var streamMap= {};
    for(var i = 0; i < mems.length; i++){
      var newm = mems[i];
      var streams = newm.streams;
      newm.streams = {};

      memMap[newm.memId] = newm; 
      for(k in streams){
        var s = streams[k];
        s.memId = newm.memId;
        streamMap[s.id] = s;
      }
    }

    // check remove streams
    var rmStreamIds = [];
    for(k in self.currentStreams){
      var s = self.currentStreams[k];
      if(!streamMap[s.id]){
        rmStreamIds.push(s.id);
      }
    }
    for(k in rmStreamIds){
      self.removeStream(rmStreamIds[k]);
    }

    // check remove members
    var rmMemIds = [];
    for(k in self.currentMembers){
      var m = self.currentMembers[k];
      if(!memMap[m.memId]){
        rmMemIds.push(m.memId);
      }
    }
    for(k in rmMemIds){
      self.removeMember(rmMemIds[k]);
    }

    // check add members
    for(k in memMap){
      var m = memMap[k];
      if(!self.getMember(m.memId)){
        self.addMember(m);
      }
    }

    // check add streams
    for(k in streamMap){
      var s = streamMap[k];
      if(!self.getStream(s.id)){
        if(!self.addStream(s)){
          return;
        }
      }
    }

    if(typeof cver !== 'undefined'){ // TODO: check type
      self.log('update cver ', cver);
      self.currentCVer = cver;
    }

  },
  
  kickTimer: function(){
    var self = this;
    if(self.timerId >= 0){
      return;
    }
    self.sendGetC(); // first all, update conference
    self.timerId = setInterval(function(){
        self.sendGetC();
        self.getPCStats();
      }, 1000);
    self.log('kick timerId ', self.timerId);
  },

  stopTimer: function(){
    var self = this;
    if(self.timerId >= 0){
      self.log('stop timerId ', self.timerId);
      clearInterval(self.timerId);
      self.timerId = -1;
    }
  },

  getPCStats: function(){
    var self = this;
    for(k in self.currPeers){
      self.doGetPCStats(k); 
    }
  },

  doGetPCStats: function(k){
    var self = this;
    var peer = self.currPeers[k];

      if(peer.pc){
        if(!peer.statsParser){
          peer.statsParser = new WebrtcStatisticsSection();  
        }
        
        peer.pc.getStats(null
          , function(results) {
            var recvstats = {};
            var sentstats = {};
            peer.statsParser.parseRecvStatistics(results, function(name, value){
                if(name === 'video-bps'){
                  recvstats.v = value;
                }else if(name === 'audio-bps'){
                  recvstats.a = value;
                }if(name == 'video-size'){
                  recvstats.sz = value;
                }
                
              }
              , function(name, value){
                if(name === 'video-bps'){
                  sentstats.v = value;
                }else if(name === 'audio-bps'){
                  sentstats.a = value;
                }
              });
            if(peer.myPublishId){
              utilFireEvent_(self, self.listener, 'onStreamStatistics', self.sessId, self.userId, peer.myPublishId, sentstats);
            }else if(peer.subsrStream){
              utilFireEvent_(self, self.listener, 'onStreamStatistics', self.sessId, peer.subsrStream.memId, peer.subsrStream.id, recvstats);
            }
            
          }
          , function(e){
            self.log("getStats error: ", e);
          }
        );
    }
  },

  checkResponseOK: function(data){
      self = this;
      if((typeof data.status === 'undefined') || (data.status < 0)) {
        self.log('error response',data);
        return false;
      }
      return true;
  },

  sendGetC: function(){
    var self = this;
    var msg = {};
    msg.op = 'extC';
    msg.subop = 'getC';
    msg.module = 'confr';
    msg.confrId = self.ticket.confrId;
    msg.cver = self.currentCVer;
    
    $.post( self.ticket.url, JSON.stringify( msg )
      , function ( data ){
        
        if(!self.checkResponseOK(data)) {
          self.log('sending extC', msg);
          self.log('error response of extC', data);
          return;
        }

        if(data.mems){
          self.log('sending extC', msg);
          self.log('response extC', data);
          self.updateMembers(data.mems, data.cver);
        }
      });

  },

  sendPubC: function(peer, first){
    var self = this;
    var msg = {};
    msg.op = 'extC';
    msg.subop = 'pubC';
    msg.module = 'confr';
    msg.confrId = self.ticket.confrId;
    msg.memId = self.ticket.userId;
    msg.rtcId = peer.currentRtcId;
    msg.first = first;
    self.log('sending pubC', msg);
    $.post( self.ticket.url, JSON.stringify( msg )
      , function ( data ){
        self.log('response pubC', data);
        if(first){
          self.fireJoined('when first pub');
        }
        if(!self.checkResponseOK(data)) {
          self.log('fire my publish event: pub fail');
          utilFireEvent_(self, self.listener, 'onStreamEvent', self.sessId, msg.memId, peer.myPublishId, 'onStreamPublishFail' );
          return;
        }
        peer.myStreamId = msg.rtcId;
        self.log('fire my publish event: pub success');
        utilFireEvent_(self, self.listener, 'onStreamEvent', self.sessId, msg.memId, peer.myPublishId, 'onStreamStartPublish' );

      });
  },

  onPeerIceSetup: function(peer){
    var self = this;
    var first = 0;
    if(!self.sessId){      
      self.makeSessionId();
      first = 1;
    }

    if(peer.local.stream){
      self.sendPubC(peer, first);  
    }else{
      self.fireJoined('when first connected');
    }

  },

  fireJoined: function(msg){
    var self = this;
    if(!self.sessId) self.makeSessionId();
    self.log('onJoined sessId ', self.sessId, msg);
    utilFireEvent_(self, self.listener, 'onJoined', self.sessId);
    self.kickTimer();

    rsdListenEventLoaded(function(installed){
      utilFireEvent_(self, self.listener, 'onShareDesktopEnable', self.sessId, installed);
    });
  },

  
  log: function(){
    var self = this;
    var arr = ['|csess-'+self.uid+'|'];
    for(var i = 0; i < arguments.length; i++){
      arr.push(arguments[i]);
    }
    console.log.apply(console, arr);
  },

} // XConferenceSession_




var RTC_PAGE_MSG_TYPE = 'RTC-SD-PAGE';
var RTC_EXT_MSG_TYPE = 'RTC-SD-EXT';
var g_extLoaded = false;
var g_pendingRequsts = [];
var g_inprogressRequsts = {};
var g_inprogressListeners = {};
var g_tsxId = 0;

function rsdExecMsg_(msg, callback){
  var req = {};
  req.callback = callback;
  req.tsxId = 'tsx-' + g_tsxId + '-' +  Math.random().toString(36).substr(2,4);

  if(!g_extLoaded){
    g_pendingRequsts.push(req);
    return;
  }
  g_inprogressRequsts[req.tsxId] = req;
  msg.tsxId = req.tsxId;
  window.postMessage(msg, '*');
}

function rsdChooseDesktopMedia(screenOptions, callback){
  if(!g_extLoaded){
    callback(null);
    return;
  }

  var msg = {type:RTC_PAGE_MSG_TYPE, evname: 'chooseDesktopMedia', screenOptions: screenOptions};
  rsdExecMsg_(msg, function(m){
    if(m.evname === 'onAccessApproved' && m.streamId){
      callback(m.streamId);
    }else{
      callback(null);
    }
  });
}

function rsdListenEventLoaded(callback){
  if(g_extLoaded){
    callback(true);
    return;
  }
  if(!g_inprogressListeners['extLoaded']){
    g_inprogressListeners['extLoaded'] = [];
  }
  g_inprogressListeners['extLoaded'].push(function(m){
    var exist = m ? true : false;
    callback(exist);  
  });
}




function rsdExtensionExist(){
  var existele = document.getElementById('RTC-Share-Deskto-installed-ele-rat1abrr');
  return existele ? true : false;
}

var ev4detect = "load";
window.addEventListener(ev4detect, function(event) {
  var exist = rsdExtensionExist();
  // console.log('exist=', exist, ', ev4detect=', ev4detect);

  if(g_extLoaded){
    return;
  }
  g_extLoaded = true;

  var evname = 'extLoaded';
  var msg = exist ? {evname: evname} : null;
  if(g_inprogressListeners[evname]){
    for(var k in g_inprogressListeners[evname]){
      g_inprogressListeners[evname][k](msg);
    }
    delete g_inprogressListeners[evname];
  }

});


window.addEventListener('message', function (event) {
  if(!event.data) return
  var msg = event.data;
  if(!msg.type || msg.type !== RTC_EXT_MSG_TYPE) return;
  if(!event.data.evname) return;
  // console.log('main: got ext-msg: ', msg);

  if(msg.evname === 'extLoaded'){
    if(!g_extLoaded){
      g_extLoaded = true;

    }
  }

  if(msg.tsxId){
    var req = g_inprogressRequsts[msg.tsxId];
    if(req && req.callback){
      delete g_inprogressRequsts[msg.tsxId];
      req.callback(msg);
    }
  }

  if(g_inprogressListeners[msg.evname]){
    for(var k in g_inprogressListeners[msg.evname]){
      g_inprogressListeners[msg.evname][k](msg);
    }
    delete g_inprogressListeners[msg.evname];
  }

});


